import parole, pygame

import random
import interface, sim, town, dungeon
from util import *
shader = parole.shader

#############################################################################

version_str = '1.0.1'

#############################################################################

titleScreenAction = None

schedule = None
mapFrame = None
cmdPoll = None
player = None
statusBox = None

# annotations
examineAnnote = None
examinedTile = None

transientAnnotes = []  # Annotations that are only displayed until the next
                       # input

waitTime = 1000 # waiting is for 1 second

levels = {}
currentLevel = None
commandToKeyMapping = None

music = {}
currentMusic = None

freeForMonsterDesc = True

#############################################################################

# Largish bodies of text to be loaded from the resource package
introText = None
deathText = None
victoryText = None
helpText = None
levelDescs = None

#############################################################################

def transient(text, posOrTile=None):
    t = None
    if posOrTile:
        if type(posOrTile) is tuple:
            t = mapFrame.getMap()[posOrTile]
        else:
            assert(type(posOrTile) is parole.map.Tile)
            t = posOrTile
    else:
        t = player.parentTile
    transientAnnotes.append(mapFrame.annotate(t, text))

#############################################################################

def loadText():
    global introText, deathText, victoryText, helpText, levelDescs
    introText = parole.resource.getResource('text/intro.txt') or 'Not Found!'
    deathText = parole.resource.getResource('text/death.txt') or 'Not Found!'
    victoryText = parole.resource.getResource('text/victory.txt') or 'Not Found!'
    helpText = parole.resource.getResource('text/help.txt') or 'Not Found!'

    introText = '{\Gold Introduction}\n' + introText
    deathText = '{\Gold Death Comes}\n' + deathText
    victoryText = '{\Gold Victory!}\n' + victoryText
    helpText = ('{\Gold Help!}\n' + helpText) % \
                                                    commandToKeyMapping

    levelDescs = {}
    levelDescs[1] = parole.resource.getResource('text/leveldesc/mines.txt')
    levelDescs[3] = parole.resource.getResource('text/leveldesc/grotto.txt')
    levelDescs[5] = parole.resource.getResource('text/leveldesc/catacombs.txt')
    levelDescs[6] = parole.resource.getResource('text/leveldesc/infernal.txt')

#############################################################################

def loadMusic():
    music['titleScreen'] = \
        parole.resource.getSound(parole.conf.interface.music.titleScreen)
    music['town'] = \
        parole.resource.getSound(parole.conf.interface.music.town)
    music['death'] = \
        parole.resource.getSound(parole.conf.interface.music.death)
    music['victory'] = \
        parole.resource.getSound(parole.conf.interface.music.victory)

#############################################################################

def playMusic(trackname, loops):
    global currentMusic
    if not parole.conf.interface.music.enabled:
        return
    if trackname not in music:
        raise ValueError('Bad music track name')

    currentMusic = music[trackname]
    currentMusic.set_volume(parole.conf.interface.music.volume)
    currentMusic.play(loops)
    return currentMusic

def fadeMusicOut(time):
    global currentMusic

    if not currentMusic:
        return

    currentMusic.fadeout(time)
    currentMusic = None

#############################################################################
# entry point

def doFrame():
    global levels, currentLevel
    # This is our main entry point, basically.

    # Fonts that get used everywhere
    global proggySmall, arial, player, schedule
    proggySmall = parole.resource.getFont('fonts/ProggySmall.ttf', 16)
    arial = parole.resource.getFont('fonts/Arial.ttf', 12)

    interface.init()
    setupCommandToKeyMapping()
    loadText()
    loadMusic()

    global titleScreenAction
    while 1:
        player = None
        schedule = None
        titleScreenAction = None
        # Start up with title screen
        parole.display.scene.removeAll()
        parole.display.clearSurface(parole.display.getSurface())
        #drawStarfield()
        showTitleScreen()
    
        # DEBUG: proceed directly to map
        #titleScreenAction = 'newGame'
        titleScreenAction = None

        # Start playing some sweet music
        if not currentMusic:
            playMusic('titleScreen', -1)
    
        # Wait till the user makes a choice
        while not titleScreenAction:
            yield
    
        # Handle the choice
        if titleScreenAction in ("quit", "newGame"):
            t = parole.time()
            fadeMusicOut(1000)
            for a in interface.fadeOut(1000):
                yield

        if titleScreenAction == "quit":
            raise parole.ParoleShutdown

        elif titleScreenAction == "help":
            weAreCurrently = {'waiting':True}
            def onClose():
                weAreCurrently['waiting'] = False
            interface.ModalBox(helpText, arial, onClose).push()
            while weAreCurrently['waiting']:
                yield

        elif titleScreenAction == "newGame":
            levels.clear()
            currentLevel = None
            weAreCurrently = {'waiting':True}

            # character creation
            rlBox = parole.shader.ReadLineBox(arial, 300, 
                prompt='Enter your name: ')
            frame = parole.shader.Frame(interface.frameBorders, 
                    contents=[parole.shader.ColorField((128,0,0,200),
                        (rlBox.width+10, rlBox.height)), rlBox])
            frame.update()
            def onEnterName(readline):
                weAreCurrently['waiting'] = False
                weAreCurrently['named'] = readline.text.strip()
                parole.display.scene.remove(frame)

            rl = parole.input.ReadLine(onEnterName, rlBox.onInput)
            parole.display.scene.add(frame, pos=frame.centeredPos())
            parole.pushUIEventHandler(rl)

            # start the town/intro music
            playMusic('town', -1)

            while weAreCurrently['waiting']:
                yield

            #parole.display.update()
            parole.info('Player name: %s', weAreCurrently['named'])

            statUpper = interface.StatUpper(6)
            statUpper.push()
            while statUpper.active:
                yield

            # A make a da playa
            schedule = sim.Schedule()
            player = sim.Player(weAreCurrently['named'],
                statUpper.str,
                statUpper.dex,
                statUpper.con,
                statUpper.per
            )

            # Let's do this thing!
            thetown, ustairPos, dstairPos = town.makeTown()
            addLevel('town', thetown, ustairPos, dstairPos)

            for frame in runSim(schedule, thetown, ustairPos):
                if player and player.dead:
                    break
                elif player and player.winner:
                    break
                yield

            # player died
            if player.dead:
                fadeMusicOut(1000)
                clearTransientAnnotations()
                mapFrame.setMap(None)
                weAreCurrently = {'waiting':True}
                def onClose():
                    weAreCurrently['waiting'] = False
                interface.ModalBox(deathText, arial, onClose).push()
                playMusic('death', 1)
                while weAreCurrently['waiting']:
                    yield
                fadeMusicOut(1000)
                for a in interface.fadeOut(1000):
                    yield

            # player won
            elif player.winner:
                fadeMusicOut(1000)
                clearTransientAnnotations()
                mapFrame.setMap(None)
                weAreCurrently = {'waiting':True}
                def onClose():
                    weAreCurrently['waiting'] = False
                interface.ModalBox(victoryText, arial, onClose).push()
                playMusic('victory', 1)
                while weAreCurrently['waiting']:
                    yield
                fadeMusicOut(1000)
                for a in interface.fadeOut(1000):
                    yield
            

#############################################################################

def showTitleScreen():
    # The main title bill (read from text/title.txt) centered high
    #parole.info(parole.display.screenTextSize(proggySmall))
    titleText = parole.resource.getResource('text/title.txt')
    tbTitle = parole.shader.TextBlockPass(proggySmall, (128,128,128), (0,0,0,0),
            text=titleText)
    tbTitle.update()
    titleRect = pygame.Rect((0,0), tbTitle.size)
    scrRect = parole.display.getSurface().get_rect()
    titleRect.centerx = scrRect.centerx
    titleRect.top = 200
    parole.display.scene.add(tbTitle, pos=titleRect.topleft)

    # Subtitle five pixels under the main
    tbSubTitle = parole.shader.TextBlockPass(arial, (255,255,255), 
            text="Halls of Darkness")
    tbSubTitle.update()
    subRect = pygame.Rect((0,0), tbSubTitle.size)
    subRect.center = titleRect.center
    subRect.top = titleRect.bottom + 5
    parole.display.scene.add(tbSubTitle, pos=subRect.topleft)

    # Horziontal lines flanking the subtitle
    lineL = parole.shader.Line((titleRect.left, subRect.centery),
                               (subRect.left, subRect.centery),
                               parole.colornames.colors['Red'])
    parole.display.scene.add(lineL, pos=lineL.defaultPos)
    lineR = parole.shader.Line((subRect.right+1, subRect.centery),
                               (titleRect.right, subRect.centery),
                               parole.colornames.colors['Red'])
    parole.display.scene.add(lineR, pos=lineR.defaultPos)

    # LineChooser in a frame
    # Choices: Start Test, Quit
    chFrame = interface.LineChooserFrame(proggySmall, 
            [('New Game', newGame),
             ('Help', help),
             ('Quit', quit)])
    chFrame.update()
    frameRect = pygame.Rect((0,0), chFrame.size)
    frameRect.centerx = titleRect.centerx
    frameRect.top = subRect.bottom + 20
    parole.display.scene.add(chFrame, pos=frameRect.topleft)

    parole.pushUIEventHandler(chFrame.chooser.cmdMap)

    # Copyright info at bottom
    tbCopy = shader.TextBlockPass(arial, (180,180,180), (0,0,0),
        text="Version %s. Copyright 2009 by Max Bane and "
             "John Greenberg. Distributed under the GPL v2." % (version_str,))
    tbCopy.update()
    copyRect = pygame.Rect((0,0), tbCopy.size)
    copyRect.bottom = scrRect.bottom-2
    copyRect.centerx = scrRect.centerx
    parole.display.scene.add(tbCopy, pos=copyRect.topleft)

#############################################################################

def quit(line):
    global titleScreenAction
    titleScreenAction = "quit"

#############################################################################

def newGame(line):
    global titleScreenAction
    titleScreenAction = "newGame"

#############################################################################

def help(line):
    global titleScreenAction
    titleScreenAction = "help"

#############################################################################

def setupCommandToKeyMapping():
    global commandToKeyMapping
    commandToKeyMapping = {}
    for key,cmd in parole.conf.interface.mainplay.keypresses.iteritems():
        if cmd not in commandToKeyMapping:
            commandToKeyMapping[cmd] = []
        commandToKeyMapping[cmd].append(key)

    for cmd in commandToKeyMapping:
        commandToKeyMapping[cmd] = r'{\Gold /}'.join(commandToKeyMapping[cmd])

#############################################################################

def setCurrentMap(map, startPos):
    global currentLevel

    if player.parentTile:
        #player.listen(sim.Event('leave tile', schedule.time, (player,
        #    player.pos), dests=[player]))
        schedule.add(sim.Event('leave tile', schedule.time, (player,
            player.pos), dests=[player]))

    clearTransientAnnotations()
    if mapFrame.getMap():
        mapFrame.setMap(None)

    mapFrame.setMap(map)
    mapFrame.centerAtTile(startPos)

    map[startPos].add(player)
    #player.listen(sim.Event('enter tile', schedule.time, (player, startPos, map),
    #    dests=[player]))
    schedule.add(sim.Event('enter tile', schedule.time, (player, startPos, map),
        dests=[player]))
    
    for level, info in levels.iteritems():
        if info[0] is map:
            currentLevel = level

    # map music
    if currentLevel == 'town':
        if not currentMusic:
            playMusic('town', -1)
    else:
        fadeMusicOut(1000)
        # todo: music for other levels than the town

#############################################################################

def addLevel(level, map, ustairPos, dstairPos):
    levels[level] = (map, ustairPos, dstairPos)

#############################################################################

def runSim(sched, curMap, startPos):
    global player, schedule, mapFrame, cmdPoll, examineAnnote, statusBox, \
           currentLevel, freeForMonsterDesc
    schedule = sched
    examineAnnote = None

    if not player:
        parole.warn('runSim: null player, creating default one.')
        player = sim.Player()

    if not curMap:
        #curMap, ustairPos, dstairPos = town.makeTown()
        curMap, ustairPos, dstairPos = dungeon.makeLevel(1)
        addLevel(1, curMap, ustairPos, dstairPos)
        startPos = ustairPos

    # set up the status display
    statusBox = interface.StatusBox(proggySmall, player)
    statusBox.update()

    # Set up a map frame 
    scrSize = parole.display.getSurface().get_size()
    mapFrame = parole.map.MapFrame((scrSize[0], scrSize[1]-statusBox.height))

    # display them
    parole.display.scene.add(mapFrame, pos=(0, statusBox.height))
    parole.display.scene.add(statusBox, pos=(statusBox.centeredPos()[0], 0))

    # be sure to unlisten before saving!
    schedule.listen('set stat', statusBox) 
    schedule.listen('increase stat', statusBox) 
    schedule.listen('decrease stat', statusBox) 
    schedule.listen('enter inventory', statusBox)
    schedule.listen('leave inventory', statusBox)

    # Set up a ui event handler for player commands
    cmap = parole.input.CommandMap(parole.conf.interface.mainplay, 
            handleCommand)
    parole.pushUIEventHandler(cmap)

    cmdPoll = CommandPoll()
    schedule.listen('get command', cmdPoll)

    # set the map and add the player to the map
    setCurrentMap(curMap, startPos)

    # start listening for commands at the next game-millisecond
    schedule.add(sim.Event('get command', 1))

    
    # show intro
    weAreCurrently = {'waiting':True}
    def onClose():
        weAreCurrently['waiting'] = False
    interface.ModalBox(introText, arial, onClose).push()

    while 1:
        if transientAnnotes and mapFrame.selectedTile:
            mapFrame.selectTile(None)

        # check for leveling
        if (player.level + 1) in player.levelXP:
            if player['xp'] >= player.levelXP[player.level+1]:
                statUpper = interface.StatUpper(4)
                statUpper.push()
                while statUpper.active:
                    yield

                player.level += 1
                schedule.add(sim.Event('set stat', schedule.time, (player,
                    'strength', statUpper.str), dests=[player]))
                schedule.add(sim.Event('set stat', schedule.time, (player,
                    'dexterity', statUpper.dex), dests=[player]))
                schedule.add(sim.Event('set stat', schedule.time, (player,
                    'constitution', statUpper.con), dests=[player]))
                schedule.add(sim.Event('set stat', schedule.time, (player,
                    'perception', statUpper.per), dests=[player]))

        while not cmdPoll.awaitCommand:
            parole.debug('ADVANCING SCHEDULE...')
            schedule.advance()

        mapFrame.update()

        # new level description
        if currentLevel in levelDescs and currentLevel not in player.levelsSeen:
            if player.tileIsBrightEnough(player.parentTile):
                freeForMonsterDesc = False
                player.levelsSeen.add(currentLevel)
                def onClose():
                    global freeForMonsterDesc
                    freeForMonsterDesc = True
                interface.ModalBox(levelDescs[currentLevel], arial, 
                        onClose).push()
        elif freeForMonsterDesc:
            # pop up monster description
            checkFirstEncounter()

        yield

def checkFirstEncounter():
    # pop up description of any new monster in fov
    for visPos in mapFrame.visibleTiles:
        t = mapFrame.getMap()[visPos]
        if not player.tileIsBrightEnough(t):
            continue
        for obj in t:
            if isinstance(obj, sim.Monster) and obj.description:
                monName = obj.name
                if monName not in player.encounteredMonsters:
                    player.encounteredMonsters.add(monName)
                    interface.ModalBox(
                            ('{\Gold First encounter with %s:}\n' %\
                                    obj.noun()) + obj.description,
                            arial,
                            lambda: None).push()
                    foundMon = True
                    return True
    return False

def clearTransientAnnotations():
    # Clear transient annotations
    for ann in transientAnnotes:
        if ann:
            mapFrame.removeAnnotation(ann)
    del transientAnnotes[:]
    if player and player.parentTile and not examineAnnote:
        mapFrame.selectTile(player.pos)

class MapSender(sim.Listener):
    def __init__(self, nm, sp):
        self.nm, self.sp = nm, sp
    def listen(self, event):
        if event.id == 'new map':
            m = interface.messageBox('Patience...')
            parole.display.update()
            setCurrentMap(self.nm, self.sp)
            schedule.unlisten('new map', self)
            schedule.add(sim.Event('get command', schedule.time+1))
            parole.display.scene.remove(m)

def handleCommand(cmd):
    global examineAnnote, examinedTile

    if not cmdPoll.awaitCommand:
        parole.error('*** Got a command while not waiting for one!')
        return

    clearTransientAnnotations()
    map = mapFrame.getMap()

    #parole.info('cmd: %s', cmd)
    if cmd == "view left":
        mapFrame.scrollTiles(-1,0)
        return
    elif cmd == "view right":
        mapFrame.scrollTiles(1,0)
        return
    elif cmd == "view down":
        mapFrame.scrollTiles(0,1)
        return
    elif cmd == "view up":
        mapFrame.scrollTiles(0,-1)
        return
    elif cmd == "quit":
        if parole.conf.options.wizard:
            raise parole.ParoleShutdown
        def __onChoose(x):
            if x == "Yes":
                schedule.add(sim.Event('set stat', schedule.time,
                    (player, 'hp', -666), dests=[player]))
                player.lastDamageSource = player
                schedule.add(sim.Event('get command', schedule.time+1))
                cmdPoll.awaitCommand = False

        interface.ObjectChooser(["No", "Yes"], __onChoose, 
                title="{\Gold Commit Suicide?}").push()
        return
        
    elif cmd == "examine":
        # Toggle examination
        if not examineAnnote:
            examineTile(player.parentTile)
            mapFrame.selectTile(None)
        else:
            # Pressing examine again: detailed examine (ie monster desc)
            for obj in examinedTile:
                if isinstance(obj, sim.Monster) and obj.description:
                    interface.ModalBox(
                            ('{\Gold %s:}\n' % obj.noun(capitalized=1)) + \
                            obj.description, 
                            arial, 
                            lambda: None).push()
                    break
        return
    elif cmd == "clear":
        # Untoggle any togglable thing that's on.
        # Includes: examine
        if examineAnnote:
            mapFrame.removeAnnotation(examineAnnote)
            examineAnnote = None
            examinedTile = None
            mapFrame.selectTile(player.pos)
        return

    elif cmd.startswith('walk '):
        # either move the player, or move the cursor if we're examining
        if not examineAnnote:
            schedule.add(sim.Event('walk', schedule.time,
                args=cmd.split()[1:], dests=[player]))
        else:
            dir = cmd.split()[1:][0]
            dx, dy = displacement(dir)
            newPos = examineAnnote.tile.col + dx, examineAnnote.tile.row + dy
            if newPos[0] < 0 or newPos[0] >= examineAnnote.tile.map.cols \
                or newPos[1] < 0 or newPos[1] >= examineAnnote.tile.map.rows:
                    return
            newTile = examineAnnote.tile.map[newPos]
            mapFrame.removeAnnotation(examineAnnote)
            examineTile(newTile)
            return
    elif (cmd == "more ambient") and parole.conf.options.wizard:
        # debug command
        map.setAmbientLight((255,255,255), min(1., map.ambientIntensity + 0.05))
        map.update()
        return
    elif (cmd == "less ambient") and parole.conf.options.wizard:
        # debug command
        map.setAmbientLight((255,255,255), max(0., map.ambientIntensity - 0.05))
        map.update()
        return
    elif (cmd == "toggle fov") and parole.conf.options.wizard:
        # debug command
        if not mapFrame.fovObj:
            mapFrame.bindVisibilityToFOV(player, player['perception'],
                remember=False, fovCondition=player.tileIsBrightEnough)
        else:
            mapFrame.bindVisibilityToFOV(None, 0)
        map.update()
        return
    elif cmd in ('open', 'close'):
        # toggle adjacent door, or ask if multiple

        def __tryToggleDoor(thedoor):
            # once the intended door is known, this tries opening it and
            # scheduling the next get-command
            evtime = player.toggleDoor(thedoor)
            if evtime < 0:
                # nothing happened; get another command
                schedule.add(sim.Event('get command', schedule.time))
            else:
                schedule.add(sim.Event('get command', evtime+1))

        def __handleDoorDir(disp):
            clearTransientAnnotations()
            dx, dy = disp
            doorPos = player.parentTile.col + dx, player.parentTile.row + dy
            if doorPos[0] < 0 or doorPos[0] >= map.cols \
                or doorPos[1] < 0 or doorPos[1] >= map.rows:
                    return
            doorTile = map[doorPos]
            for obj in doorTile:
                if isinstance(obj, sim.Door):
                    __tryToggleDoor(obj)
                    cmdPoll.awaitCommand = False
                    return
            transientAnnotes.append(mapFrame.annotate(doorTile,
                "There is no door there."))

        def __handleDoorDirCancel():
            clearTransientAnnotations()

        door = None
        for t in adjacentTiles(player.parentTile):
            for obj in t:
                if isinstance(obj, sim.Door):
                    if not door:
                        door = obj
                    else:
                        # there are multiple doors
                        transientAnnotes.append(mapFrame.annotate(player.parentTile,
                            "Which direction?"))
                        interface.DirectionChooser(__handleDoorDir,
                                __handleDoorDirCancel).push()
                        return
                        
        if door:
            __tryToggleDoor(door)
        else:
            transientAnnotes.append(mapFrame.annotate(player.parentTile,
                "There is no door nearby."))
            return

    elif cmd == 'get':
        def __tryPickup(item):
            evtime = player.pickupItem(item)
            if evtime < 0:
                # nothing happened; get another command
                schedule.add(sim.Event('get command', schedule.time))
            else:
                schedule.add(sim.Event('get command', evtime + 1))

        def __handleChooseGet(item):
            cmdPoll.awaitCommand = False
            __tryPickup(item)
            return

        # find an item in the player's tile
        target = None
        for obj in player.parentTile:
            if isinstance(obj, sim.Item):
                if target:
                    # there are multiple possible items
                    interface.ObjectChooser(
                            [o for o in player.parentTile if isinstance(o,
                                sim.Item)],
                            __handleChooseGet,
                            title="{\Gold Pick up which item?}").push()
                    return
                else:
                    target = obj
        if target:
            __tryPickup(target)
        else:
            transientAnnotes.append(mapFrame.annotate(player.parentTile,
                "There is nothing here that can be picked up."))
            return
        
    elif cmd == 'drop':
        def __doDrop(target):
            evtime = player.dropItem(target)
            if evtime < 0:
                # nothing happened; get another command
                schedule.add(sim.Event('get command', schedule.time))
            else:
                schedule.add(sim.Event('get command', evtime + 1))
            return

        def __handleChooseDrop(target):
            cmdPoll.awaitCommand = False
            __doDrop(target)
            return

        if not player.inventory:
            transientAnnotes.append(mapFrame.annotate(player.parentTile,
                "You have nothing to drop."))
            return
        if len(player.inventory) == 1:
            target = player.inventory[0]
            __doDrop(target)
        else:
            # choose what to drop
            interface.ObjectChooser(player.inventory, __handleChooseDrop,
                    title="{\Gold Drop which item?}").push()
            return

    elif (cmd == "toggle clipping") and parole.conf.options.wizard:
        player.clipping = not player.clipping
        return

    elif cmd == "inventory":
        if not player.inventory:
            transient("You are unburdened by the sin of possession.")
            return

        interface.ObjectChooser(player.inventory, lambda target: 0, 
            title="{\Gold You carry the following, for a total weight of %s lbs "
            "(capacity %s lbs):}" % (player.inventoryWeight(),
                player.inventoryCapacity())).push()
        return

    elif cmd == "wear":
        targets = []
        for obj in player.inventory:
            if isinstance(obj, sim.Wearable) and obj not in \
                    player.wearSlots.values():
                targets.append(obj)

        if not targets:
            transient("You have nothing that could be worn or wielded.")
            return

        def __handleChooseWearable(target):
            cmdPoll.awaitCommand = False
            evtime = player.wear(target)
            if evtime < 0:
                # nothing happened; get another command
                schedule.add(sim.Event('get command', schedule.time))
            else:
                schedule.add(sim.Event('get command', evtime + 1))
            

        interface.ObjectChooser(targets, __handleChooseWearable, 
            title="{\Gold Wear or wield what?}").push()
        return

    elif cmd == "remove":
        targets = []
        for obj in player.wearSlots.values():
            if obj:
                targets.append(obj)

        if not targets:
            transient("You've nothing to remove or unwield.")
            return

        def __handleChooseWearable(target):
            cmdPoll.awaitCommand = False
            evtime = player.remove(target)
            if evtime < 0:
                # nothing happened; get another command
                schedule.add(sim.Event('get command', schedule.time))
            else:
                schedule.add(sim.Event('get command', evtime + 1))

        interface.ObjectChooser(targets, __handleChooseWearable, 
            title="{\Gold Remove or unwield what?}").push()
        return

    elif cmd == "equipment":
        equip = []
        for obj in player.wearSlots.values():
            if obj:
                equip.append(obj)
        if not equip:
            transient("You've nothing worn or wielded.")
            return

        interface.ObjectChooser(equip, lambda target: 0, 
            title="{\Gold You wear and wield the following:}" ).push()
        return

    elif (cmd == "toggle monster reporting") and parole.conf.options.wizard:
        player.monsterReporting = not player.monsterReporting
        return

    elif (cmd == "wait"):
        schedule.add(sim.Event('get command', schedule.time+1000))

    elif (cmd == "light"):
        lights = []
        existingSource = None
        for obj in player.inventory:
            if isinstance(obj, sim.LightSource):
                if obj.on:
                    existingSource = obj
                else:
                    # we can light carried lightsources 
                    lights.append(obj)
        for obj in player.parentTile:
            if isinstance(obj, sim.LightSource) and not obj.on:
                # we can light lightsources on the ground
                lights.append(obj)

        if not lights:
            transient("There is nothing nearby to light.")
            return

        def __handleChooseLight(target):
            cmdPoll.awaitCommand = False
            evtime = player.light(target)
            if evtime < 0:
                # nothing happened; get another command
                schedule.add(sim.Event('get command', schedule.time))
            else:
                schedule.add(sim.Event('get command', evtime + 1))
            

        interface.ObjectChooser(lights, __handleChooseLight, 
                title="{\Gold Choose an item to light with your %s:}" %\
                        (existingSource and existingSource.noun(proper=1) \
                         or 'flint',)).push()
        return

    elif cmd == 'snuff':
        lights = []
        existingSource = None
        for obj in player.inventory:
            if isinstance(obj, sim.LightSource):
                if obj.on:
                    lights.append(obj)
        for obj in player.parentTile:
            if isinstance(obj, sim.LightSource) and obj.on:
                # we can light lightsources on the ground
                lights.append(obj)

        if not lights:
            transient("There is no lightsource nearby to snuff.")
            return

        def __handleChooseSnuff(target):
            cmdPoll.awaitCommand = False
            evtime = player.snuff(target)
            if evtime < 0:
                # nothing happened; get another command
                schedule.add(sim.Event('get command', schedule.time))
            else:
                schedule.add(sim.Event('get command', evtime + 1))
            

        interface.ObjectChooser(lights, __handleChooseSnuff, 
                title="{\Gold Choose a light to snuff:}").push()
        return

    elif cmd.startswith('makelevel '):
        depth = int(cmd[-1])
        setCurrentMap(*(dungeon.makeLevel(depth)[:2]))
        schedule.add(sim.Event('get command', schedule.time+2))

    elif cmd == 'descend':
        thisLevel = currentLevel
        thisMap, thisStartPos, thisDownPos = levels[currentLevel]
        if player.pos != thisDownPos:
            transient('There is no way down here.')
            return

        if currentLevel == 'town':
            nextLevel = 1
        else:
            nextLevel = currentLevel + 1
        if nextLevel not in levels:
            thelevel, ustairPos, dstairPos = dungeon.makeLevel(nextLevel)
            addLevel(nextLevel, thelevel, ustairPos, dstairPos)
        nextMap, startPos, dstairPos = levels[nextLevel]

        schedule.listen('new map', MapSender(nextMap, startPos))
        schedule.add(sim.Event('new map', schedule.time + \
                (thisLevel != 'town' and 30000 or 2000)))

    elif cmd == 'ascend':
        thisLevel = currentLevel
        thisMap, thisUpPos, thisDownPos = levels[currentLevel]
        if player.pos != thisUpPos:
            transient('There is no way up here.')
            return

        if currentLevel == 'town':
            transient('There is no way up here.')
            return
        elif currentLevel == 1:
            nextLevel = 'town'
        else:
            nextLevel = currentLevel - 1

        if nextLevel not in levels:
            thelevel, ustairPos, dstairPos = dungeon.makeLevel(nextLevel)
            addLevel(nextLevel, thelevel, ustairPos, dstairPos)

        if nextLevel == 'town':
            nextMap, xxx, startPos = levels[nextLevel]
        else:
            nextMap, startPos, dstairPos = levels[nextLevel]

        schedule.listen('new map', MapSender(nextMap, startPos))
        schedule.add(sim.Event('new map', schedule.time + \
                (thisLevel != 'town' and 30000 or 2000)))

    elif cmd == 'quaff':
        targets = []
        for obj in player.inventory:
            if isinstance(obj, sim.Potion):
                targets.append(obj)

        if not targets:
            transient("You have no potions.")
            return

        def __handleChoosePotion(target):
            cmdPoll.awaitCommand = False
            evtime = player.quaff(target)
            if evtime < 0:
                # nothing happened; get another command
                schedule.add(sim.Event('get command', schedule.time))
            else:
                schedule.add(sim.Event('get command', evtime + 1))
            

        interface.ObjectChooser(targets, __handleChoosePotion, 
            title="{\Gold Quaff which potion?}").push()
        return

    elif cmd == 'read':
        if not player.tileIsBrightEnough(player.parentTile):
            transient("It is too dark here to read.")
            return

        targets = []
        for obj in player.inventory:
            if isinstance(obj, sim.Scroll):
                targets.append(obj)

        if not targets:
            transient("You have no scrolls.")
            return

        def __handleChooseScroll(target):
            cmdPoll.awaitCommand = False
            evtime = player.read(target)
            if evtime < 0:
                # nothing happened; get another command
                schedule.add(sim.Event('get command', schedule.time))
            else:
                schedule.add(sim.Event('get command', evtime + 1))
            

        interface.ObjectChooser(targets, __handleChooseScroll, 
            title="{\Gold Read which scroll?}").push()
        return

    elif cmd == 'help':
        def __onClose():
            pass

        interface.ModalBox(helpText, arial, __onClose).push()
        return

    else:
        parole.warn('Unhandled command: %r', cmd)
        return

    cmdPoll.awaitCommand = False

#========================================

class CommandPoll(sim.Listener):
    awaitCommand = False

    def listen(self, event):
        if event.id == 'get command':
            # check for caps lock warning
            if pygame.key.get_mods() & pygame.KMOD_CAPS:
                transient('Warning: Your caps lock key is active!')
                parole.warn('Your caps lock key is active!')

            self.awaitCommand = True
            parole.debug('\n\n\nAwaiting command [time=%d]...', schedule.time)

#========================================

def reportMonster(tile):
    for obj in tile:
        if isinstance(obj, sim.Creature):
            parole.info('Reporting on: %r', obj)
            for statname in obj.statNames:
                if 'Mod' not in statname:
                    total = obj[statname]
                    base = getattr(obj, statname)
                    try:
                        mod = getattr(obj, statname+'Mod')
                    except AttributeError:
                        mod = 'n/a'
                    parole.info('\t%s = %s = %s + %s', statname, total, base, mod)

#========================================

def examineTile(tile):
    global examineAnnote, examinedTile

    if (tile.col, tile.row) in mapFrame.visibleTiles \
            or (not mapFrame.fovObj):
        examineText = 'You see: %s.' % ', '.join([x.noun() for x in \
                player.visibleObjectsAt(tile)])
    else:
        examineText = 'You cannot see there.'

    examineText += '\n{\Gold [%s to clear]}' % (commandToKeyMapping['clear'],)
    hasMonster = False
    for obj in player.visibleObjectsAt(tile):
        if isinstance(obj, sim.Monster) and obj.description:
            hasMonster = True
            break
    if hasMonster:
        examineText += '\n{\Gold [%s for monster description]}' % \
                (commandToKeyMapping['examine'],)
    examineAnnote = mapFrame.annotate(tile, examineText)
    examinedTile = tile
        
    if player.monsterReporting:
        reportMonster(tile)
